Add ASIM Network Session parser and hunting queries for AWS GuardDuty - #14769
Add ASIM Network Session parser and hunting queries for AWS GuardDuty#14769OluOlus wants to merge 5 commits into
Conversation
Adds ASimNetworkSessionAWSGuardDuty, querying the AWSGuardDuty table directly with no dependency on any private/unsubmitted function, plus three hunting queries: high-severity findings clustered by account and actor, EKS privilege escalation/credential access, and public or weakly-protected S3 buckets.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds AWS GuardDuty hunting content and an ASIM Network Session parser to make GuardDuty findings more usable for cross-source hunting and normalized analytics.
Changes:
- Added 3 new GuardDuty hunting queries (S3 exposure, high-severity clustering, EKS privilege escalation/credential access).
- Added
ASimNetworkSessionAWSGuardDutyparser targeting ASIM Network Session schema v0.2.6. - Introduced entity mappings for hunting query results (account/actor) and ASIM normalization fields for GuardDuty network-context findings.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_S3PublicExposure.yaml | New hunting query to group S3 public exposure / weak protection findings by bucket. |
| Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_HighSeverityFindingsCrossAccount.yaml | New hunting query to cluster high-severity findings by account/activity/actor. |
| Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_EKSPrivilegeEscalationCredentialAccess.yaml | New hunting query to investigate EKS/Kubernetes privilege escalation and credential access patterns. |
| Parsers/ASimNetworkSession/Parsers/ASimNetworkSessionAWSGuardDuty.yaml | New ASIM NetworkSession parser to normalize GuardDuty findings that include network context. |
- Parser: drop DNS_REQUEST (no remoteIpDetails to normalize), resolve Src/Dst by connectionDirection instead of always treating the AWS resource as Src, treat AWS_API_CALL's caller IP as Src with no fabricated destination, use TimeCreated instead of ingestion time for ASIM event timestamps. - Hunting queries: remove Account entity mappings keyed on fields that mix instance ids, usernames, and the literal "unknown" (high-severity and EKS queries); stop folding the caller IP into the S3 query's actor field now that it has its own CallerIp column.
|
Thanks for the review — pushed a fix addressing all 9 comments:
|
@microsoft-github-policy-service agree company="Microsoft" |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (10)
Parsers/ASimNetworkSession/Parsers/ASimNetworkSessionAWSGuardDuty.yaml:129
NetworkDirectionwill be"Unknown"forPORT_PROBE(and likelyAWS_API_CALL) becauseNetworkAction_is null for those action types. This contradicts the earlier logic that treatsPORT_PROBEas effectively inbound. Consider derivingNetworkDirectionfromIsInbound_/IsApiCall_(e.g., forcePORT_PROBEto"Inbound", and set API calls to a consistent value such as"Unknown"or a documented convention).
NetworkDirection = case(
tostring(NetworkAction_.connectionDirection) == "INBOUND", "Inbound",
tostring(NetworkAction_.connectionDirection) == "OUTBOUND", "Outbound",
"Unknown"
),
Parsers/ASimNetworkSession/Parsers/ASimNetworkSessionAWSGuardDuty.yaml:151
DvcActionis derived fromNetworkAction_.blocked, butNetworkAction_is null forAWS_API_CALLandPORT_PROBE, which will default these records to"Allow". That’s misleading for non-network-connection findings. Consider settingDvcActiononly forNETWORK_CONNECTION(and otherwise set to empty/unknown), or compute it perActionType_.
DvcAction = iff(tobool(NetworkAction_.blocked) == true, "Deny", "Allow"),
Parsers/ASimNetworkSession/Parsers/ASimNetworkSessionAWSGuardDuty.yaml:132
- Setting
NetworkBytes/NetworkPacketsto0implies a confirmed zero-byte/zero-packet session, which is different from “unknown/not provided by GuardDuty”. Prefer using typed nulls so downstream analytics don’t treat these as actual measured zeros.
NetworkBytes = int(0),
NetworkPackets = int(0),
Parsers/ASimNetworkSession/Parsers/ASimNetworkSessionAWSGuardDuty.yaml:44
PORT_PROBEfindings can contain multipleportProbeDetailsentries, but the parser hard-codes[0], discarding additional probed ports/IP details. Considermv-expandoverportProbeDetails(one normalized row per probe detail) or aggregating details into sets (e.g., ports) depending on the intended ASIM cardinality.
ActionType_ == "PORT_PROBE", PortProbeAction_.portProbeDetails[0].remoteIpDetails,
Parsers/ASimNetworkSession/Parsers/ASimNetworkSessionAWSGuardDuty.yaml:59
PORT_PROBEfindings can contain multipleportProbeDetailsentries, but the parser hard-codes[0], discarding additional probed ports/IP details. Considermv-expandoverportProbeDetails(one normalized row per probe detail) or aggregating details into sets (e.g., ports) depending on the intended ASIM cardinality.
ActionType_ == "PORT_PROBE", toint(PortProbeAction_.portProbeDetails[0].localPortDetails.port),
Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_HighSeverityFindingsCrossAccount.yaml:30
- If
Idis unique per GuardDuty finding row (typical for finding IDs),dcount(Id)adds unnecessary overhead versuscount(). Consider replacingFindingCount = dcount(Id)withFindingCount = count()(and apply the same change in the other new hunting queries that usedcount(Id)).
FindingCount = dcount(Id),
DistinctFindingTypes = dcount(ActivityType),
Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_S3PublicExposure.yaml:24
- Indexing
s3BucketDetails[0]can yield empty strings when the array is missing/empty, which can cause unrelated findings to collapse into a single group (emptyBucketName/BucketArn). Consider filtering to require a non-empty bucket identifier (preferably ARN), or handling absent/multi-entrys3BucketDetailsexplicitly before summarizing.
BucketName = tostring(ResourceDetails.s3BucketDetails[0].name),
BucketArn = tostring(ResourceDetails.s3BucketDetails[0].arn),
Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_S3PublicExposure.yaml:37
- Indexing
s3BucketDetails[0]can yield empty strings when the array is missing/empty, which can cause unrelated findings to collapse into a single group (emptyBucketName/BucketArn). Consider filtering to require a non-empty bucket identifier (preferably ARN), or handling absent/multi-entrys3BucketDetailsexplicitly before summarizing.
by AccountId, Region, ThreatPurpose, BucketName, BucketArn
Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_HighSeverityFindingsCrossAccount.yaml:26
- As written,
RemoteIpcan become an empty string when neither source exists, andmake_set(RemoteIp, 10)will then include""in results. Consider filtering out emptyRemoteIpvalues before summarizing, or using a conditional set pattern so the output isn’t polluted with empty entries.
RemoteIp = coalesce(
tostring(ServiceDetails.action.networkConnectionAction.remoteIpDetails.ipAddressV4),
tostring(ServiceDetails.action.awsApiCallAction.remoteIpDetails.ipAddressV4)
)
Solutions/Amazon Web Services/Hunting Queries/AWS_GuardDuty_HighSeverityFindingsCrossAccount.yaml:32
- As written,
RemoteIpcan become an empty string when neither source exists, andmake_set(RemoteIp, 10)will then include""in results. Consider filtering out emptyRemoteIpvalues before summarizing, or using a conditional set pattern so the output isn’t polluted with empty entries.
FindingTypes = make_set(ActivityType, 10),
RemoteIps = make_set(RemoteIp, 10),
Parser: - Expand PORT_PROBE's portProbeDetails array instead of hard-coding index [0], so multi-port probes normalize to one row per probe. - Derive NetworkDirection from the same Src/Dst direction logic used elsewhere, not raw connectionDirection (which is null for AWS_API_CALL/PORT_PROBE and was always falling through to "Unknown"). - NetworkBytes/NetworkPackets are now typed nulls, not 0 - GuardDuty doesn't report counts, so 0 would misrepresent an unknown as measured. - DvcAction is now only set for NETWORK_CONNECTION; AWS_API_CALL and PORT_PROBE no longer default to a fabricated "Allow". Hunting queries: - FindingCount now uses count() instead of dcount(Id); Id is already unique per finding row. - S3 query drops rows with no bucket ARN before summarizing, instead of silently collapsing them into one group with an empty BucketName/Arn. - Set aggregations (RemoteIps, Workloads, Namespaces, CallerIps, PublicAccessStates, Actors) use make_set_if(..., isnotempty(...)) instead of make_set, so empty strings don't pollute the output.
|
Pushed a fix for the second round of automated review feedback (10 low-confidence comments, all valid):
|
Adds the 3 new hunting queries to Data/Solution_AmazonWebServices.json and bumps the solution version to 3.0.11 with a ReleaseNotes.md entry, per maintainer guidance that content added to an existing Solution needs to be registered in its manifest. Does not regenerate Package/mainTemplate.json or produce a new package zip - left to the packaging pipeline/maintainer given the blast radius of repackaging the whole existing solution.
Adds
ASimNetworkSessionAWSGuardDuty(ASIM Network Session schema v0.2.6) and three hunting queries to the Amazon Web Services solution, extending GuardDuty coverage beyond the existing analytic rule with structured, cross-source-hunt-ready content.AWSGuardDutytable directly — no dependency on any private/unsubmitted function.Related discussion: #14768